package xyz.heyaoshare.utils.file;

import cn.hutool.core.io.FileUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import static xyz.heyaoshare.exceptions.code.ErrorInfo.MINIO_FILE_CONVERSION_FAILED;
import static xyz.heyaoshare.exceptions.customize.ServerException.exception;

/**
 * 文件工具类【基于hu-tool】
 * @author YueHe
 * @version 0.0.1
 * @since 2025/6/27 上午10:06
 */
@Slf4j
public class FileUtils extends FileUtil {

    /**
     * 将MultipartFile对象转换为File对象
     *
     * @param multipartFile MultipartFile对象
     * @return 转换后的File对象
     * @throws xyz.heyaoshare.exceptions.customize.ServerException 如果在转换过程中发生异常，将抛出异常
     */
    public static File multipartFileToFile(MultipartFile multipartFile) {
        try {
            File tempFile = File.createTempFile("temp-", multipartFile.getOriginalFilename());
            multipartFile.transferTo(tempFile);
            return tempFile;
        } catch (IOException e) {
            throw exception(MINIO_FILE_CONVERSION_FAILED);
        }
    }

    /**
     * 计算给定文件的SHA-1哈希值。
     *
     * @param file 需要计算SHA-1哈希值的文件
     * @return 计算得到的SHA-1哈希值的十六进制字符串表示
     */
    public static String calculateSHA1(File file) {
        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-1");
            try (FileInputStream fis = new FileInputStream(file)) {
                byte[] buffer = new byte[8192];
                int bytesRead;
                while ((bytesRead = fis.read(buffer)) != -1) {
                    digest.update(buffer, 0, bytesRead);
                }
            } catch (IOException e) {
                log.error("Read file failed: {}", e.getMessage());
                return null;
            }

            byte[] hashBytes = digest.digest();
            return bytesToHex(hashBytes);
        } catch (NoSuchAlgorithmException e) {
            log.error("SHA-1 algorithm not found: {}", e.getMessage());
            return null;
        }
    }

    /**
     * 通过 commons-codec依赖 计算文件的SHA1值
     * <p>需要引入 commons-codec 依赖</p>
     * @param file 要计算SHA1值的文件
     * @return 文件的SHA1值，以十六进制字符串表示
     */
    public static String calculateSHA1ByUtils(File file) {
        try (FileInputStream fis = new FileInputStream(file)) {
            return DigestUtils.sha1Hex(fis);
        } catch (Exception e) {
            log.error("Error calculating SHA1 for file: {}", e.getMessage());
            return null;
        }
    }

    /**
     * 使用 commons-codec依赖 计算MultipartFile对象的SHA1值
     * <p>需要引入 commons-codec 依赖</p>
     * @param multipartFile MultipartFile对象
     * @return 文件的SHA1哈希值，返回类型为字符串
     */
    public static String calculateSHA1ByUtils(MultipartFile multipartFile) {
        try (InputStream is = multipartFile.getInputStream()) {
            return DigestUtils.sha1Hex(is);
        } catch (Exception e) {
            log.error("Error calculating SHA1 for file: {}", e.getMessage());
            return null;
        }
    }

    /**
     * 计算MultipartFile的MD5值
     * @param file MultipartFile对象
     * @return 文件的MD5哈希值(32位小写)，如果出错返回null
     */
    public static String getFileMD5(MultipartFile file) {
        if (file == null || file.isEmpty()) {
            return null;
        }

        try (InputStream inputStream = file.getInputStream()) {
            return calculateMD5(inputStream);
        } catch (Exception e) {
            log.error("Error calculating MD5 for file: {}", e.getMessage());
            return null;
        }
    }

    /**
     * 计算文件的MD5值
     * @param file 文件对象
     * @return 文件的MD5哈希值(32位小写)，如果出错返回null
     */
    public static String getFileMD5(File file) {
        if (file == null || !file.exists() || !file.isFile()) {
            return null;
        }

        try (FileInputStream fis = new FileInputStream(file)) {
            return calculateMD5(fis);
        } catch (IOException e) {
            log.error("Error calculating MD5 for file: {}", e.getMessage());
            return null;
        }
    }

    /**
     * 获取文件扩展名（不带点）
     * @param file MultipartFile对象
     * @param isDot 是否包含点，true表示返回带点的扩展名（例如".jpg"），false表示不带点（"jpg"）
     * @return 文件扩展名（小写），如"jpg"、"png"。如果没有扩展名则返回空字符串
     */
    public static String getFileExtension(MultipartFile file, boolean isDot) {
        if (file == null || file.isEmpty()) {
            return "";
        }

        String originalFilename = file.getOriginalFilename();
        if (originalFilename == null || originalFilename.lastIndexOf(".") == -1) {
            return "";
        }

        int index = originalFilename.lastIndexOf(".");
        return originalFilename.substring(isDot ? index : index + 1).toLowerCase();
    }

    /**
     * 创建临时文本文件
     * @param content 文件内容
     * @return 创建的临时文件路径
     */
    public static File createTempTextFile(String content) {
        try {
            // 创建临时文件（会自动添加随机后缀防止冲突）
            Path tempFile = Files.createTempFile("temp-", ".txt");

            // 写入内容（使用UTF-8编码）
            Files.writeString(tempFile, content, StandardOpenOption.WRITE);

            return tempFile.toFile();
        } catch (IOException e) {
            log.error("创建临时文件失败: {}", e.getMessage());
            throw new RuntimeException("Failed to create temporary text file", e);
        }
    }


    /**
     * 将File对象转换为MultipartFile对象
     *
     * @param file 要转换的File对象
     * @return 转换后的MultipartFile对象
     */
    public static MultipartFile FileToMultipartFile(File file) {
        return new MultipartFile() {
            @NotNull
            @Override
            public String getName() {
                return file.getName();
            }

            @Override
            public String getOriginalFilename() {
                return file.getName();
            }

            @Override
            public String getContentType() {
                try {
                    String contentType = Files.probeContentType(file.toPath());
                    return contentType != null ? contentType : "application/octet-stream";
                } catch (IOException e) {
                    return "application/octet-stream";
                }
            }

            @Override
            public boolean isEmpty() {
                return file.length() == 0;
            }

            @Override
            public long getSize() {
                return file.length();
            }

            @NotNull
            @Override
            public byte[] getBytes() throws IOException {
                return Files.readAllBytes(file.toPath());
            }

            @NotNull
            @Override
            public InputStream getInputStream() throws IOException {
                return new FileInputStream(file);
            }

            @Override
            public void transferTo(@NotNull File dest) throws IOException, IllegalStateException {
                Files.copy(file.toPath(), dest.toPath(), StandardCopyOption.REPLACE_EXISTING);
            }
        };
    }


    /**
     * 将字节数组转换为十六进制字符串
     * @param bytes 字节数组
     * @return 十六进制字符串
     */
    private static String bytesToHex(byte[] bytes) {
        StringBuilder hexString = new StringBuilder();
        for (byte b : bytes) {
            String hex = Integer.toHexString(0xff & b);
            if (hex.length() == 1) {
                hexString.append('0');
            }
            hexString.append(hex);
        }
        return hexString.toString();
    }

    /**
     * 计算输入流的MD5值
     * @param inputStream 输入流
     * @return MD5哈希值(32位小写)
     * @throws IOException 读取流异常
     */
    private static String calculateMD5(InputStream inputStream) throws IOException {
        try {
            MessageDigest digest = MessageDigest.getInstance("MD5");
            byte[] buffer = new byte[8192];
            int bytesRead;

            while ((bytesRead = inputStream.read(buffer)) != -1) {
                digest.update(buffer, 0, bytesRead);
            }

            byte[] md5Bytes = digest.digest();
            return bytesToHex(md5Bytes);
        } catch (Exception e) {
            throw new RuntimeException("计算MD5失败", e);
        }
    }

    /**
     * 根据文件大小计算合适的缓冲区大小
     * @param fileSize 文件大小(字节)
     * @return 缓冲区大小(字节)
     */
    public static int calculateBufferSize(long fileSize) {
        // 小文件(<1MB): 使用8KB缓冲区
        if (fileSize <= 1024 * 1024) {
            return 8 * 1024;
        }
        // 中等文件(1MB-100MB): 使用64KB缓冲区
        else if (fileSize <= 100 * 1024 * 1024) {
            return 64 * 1024;
        }
        // 大文件(>100MB): 使用256KB缓冲区
        else {
            return 256 * 1024;
        }
    }

    /**
     * 对文件名进行URL编码
     *
     * @param fileName 要编码的文件名
     * @return 编码后的文件名
     */
    public static String encodeFileName(String fileName) {
        return URLEncoder.encode(fileName, StandardCharsets.UTF_8)
                .replaceAll("\\+", "%20");
    }


    /**
     * 格式化文件大小显示，自动选择最合适的单位
     * @param size 文件大小（字节）
     * @return 格式化后的字符串，包含单位和最多两位小数
     */
    public static String formatFileSize(long size) {
        if (size <= 0) {
            return "0B";
        }

        // 定义单位
        final String[] units = new String[]{"B", "KB", "MB", "GB", "TB"};
        // 计算单位索引
        int digitGroups = (int) (Math.log10(size) / Math.log10(1024));
        // 确保不超过单位数组长度
        digitGroups = Math.min(digitGroups, units.length - 1);

        // 计算大小并格式化
        double formattedSize = size / Math.pow(1024, digitGroups);

        // 根据大小决定小数位数
        String formatPattern;
        if (formattedSize < 10) {
            formatPattern = "%.2f"; // 小于10显示2位小数
        } else if (formattedSize < 100) {
            formatPattern = "%.1f"; // 小于100显示1位小数
        } else {
            formatPattern = "%.0f"; // 大于等于100显示整数
        }

        return String.format(formatPattern + "%s", formattedSize, units[digitGroups]);
    }

}
